package files

import (
	"bufio"
	"errors"
	"fmt"
	"io/fs"
	"os"
	"path/filepath"
	"strings"

	"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
	"github.com/redhat-developer/odo/pkg/util"
)

const _dotOdoGenerated = "generated"

// ReportLocalFileGeneratedByOdo appends the specified file path to the .odo/generated file,
// taking care of creating it if it does not exist.
// The file path can be absolute or relative to the root directory specified.
// This function should be called by odo commands that generate a file, anytime a new file is generated by odo.
// This way, we would be able to determine (via GetFilesGeneratedByOdo) if a file was created by odo or not,
// in case we need for example to delete it.
//
// Note that it is quite impossible with this approach to cover all scenarios;
// for example, if an odo command generates a new file (and calls ReportLocalFileGeneratedByOdo as expected),
// but the user later deletes it and recreates it, GetFilesGeneratedByOdo would still return this file path.
func ReportLocalFileGeneratedByOdo(filesys filesystem.Filesystem, rootDirectory string, filePath string) error {
	dotOdoDirectory := filepath.Join(rootDirectory, util.DotOdoDirectory)
	err := filesys.MkdirAll(dotOdoDirectory, 0755)
	if err != nil {
		return err
	}

	odoGeneratedFile := filepath.Join(rootDirectory, util.DotOdoDirectory, _dotOdoGenerated)
	f, err := filesys.OpenFile(odoGeneratedFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		return fmt.Errorf("unable to read or create file %q: %w", odoGeneratedFile, err)
	}
	defer f.Close()

	relativePath := filePath
	if filepath.IsAbs(filePath) && strings.HasPrefix(filePath, rootDirectory) {
		relativePath, err = filepath.Rel(rootDirectory, filePath)
		if err != nil {
			return err
		}
	}
	_, err = f.WriteString(relativePath + "\n")
	return err
}

// GetFilesGeneratedByOdo returns a list of files that were initially created by odo.
// Under the hood, it works by reading the content of the .odo/generated file (ignoring blank lines),
// which is filled by any command that generates a file.
// odo commands that generate files should call ReportLocalFileGeneratedByOdo anytime a file is generated.
// Note that the .odo directory itself is always included in the list returned.
// No error is returned if the .odo/generated file does not exist.
func GetFilesGeneratedByOdo(filesys filesystem.Filesystem, rootDirectory string) ([]string, error) {
	list := []string{util.DotOdoDirectory}

	odoGeneratedFile := filepath.Join(rootDirectory, util.DotOdoDirectory, _dotOdoGenerated)
	f, err := filesys.OpenFile(odoGeneratedFile, os.O_RDWR, 0666)
	if err != nil {
		if errors.Is(err, fs.ErrNotExist) {
			return list, nil
		}
		return list, err
	}
	defer f.Close()

	scanner := bufio.NewScanner(f)
	scanner.Split(bufio.ScanLines)
	for scanner.Scan() {
		line := scanner.Text()
		if line == "" || strings.TrimSpace(line) == "" {
			continue
		}
		list = append(list, line)
	}

	return list, nil
}
